#include "StdAfx.h"
#include "Raytracer.h"
#include "Point3D.h"
#include "Ray.h"

#include "Camera.h"
#include "Canvas.h"

#include "Primitive.h"
#include "Material.h"
#include "Light.h"
#include "Scene.h"
CRaytracer::CRaytracer(CScene& _scene, CCanvas& _canvas)
: scene(_scene)
, canvas(_canvas)
{	

}
CRaytracer::~CRaytracer(void)
{
}
void CRaytracer::Trace()
{
	CCamera* Camera = scene.GetCamera();
	
	// Calculate Camera knowledge.
	CPoint3D Eye = Camera->GetEye();
	CPoint3D LookAt = Camera->GetLookAt();
	CPoint3D PlaneNormal = Eye - LookAt;
	CPoint3D UnitX = CPoint3D(0,0,1).Cross(PlaneNormal);
	CPoint3D UnitY = UnitX.Cross(PlaneNormal);
	PlaneNormal.Normalize();
	UnitX.Normalize();
	UnitY.Normalize();

	// We now know the window between we have to render the pixels. 
	// Shoot ray through every pixel. 
	float width = Camera->GetWidth();
	float height= Camera->GetHeight();
	for (int i=0;i<canvas.GetScreenHeight();++i)
		for (int j=0;j<canvas.GetScreenWidth();++j)
		{
			CPoint3D Target = LookAt;
			Target += (UnitX * width / 2.f);
			Target += (UnitY * height / 2.f);
			Target -= UnitX * (float)j * width / canvas.GetScreenWidth();
			Target -= UnitY * (float)i * height / canvas.GetScreenHeight();
			
			CPoint3D Direction = Target - Eye;
			Direction.Normalize();
			CRay ray(Eye,Direction);
			CColor color(0,0,0);

			Raytrace(ray,color,1);

			int offset = (i * canvas.GetScreenWidth() + j) * 3;

			canvas.GetScreenData()[offset + 0] = color.Red;
			canvas.GetScreenData()[offset + 1] = color.Green;
			canvas.GetScreenData()[offset + 2] = color.Blue;			
			
		}
}

void CRaytracer::Raytrace(CRay& ray, CColor& color, int depth)
{
	if (depth > MAX_DEPTH) return;

	CPoint3D intersection;
	CPoint3D normal;
	float dist;
	CPrimitive* primitive=GetClosestShape(ray,dist,intersection,normal);
	if (primitive)
	{
		// SHADING
		std::vector<CLight*>& Lights = scene.GetLights();
		for (size_t k=0;k<Lights.size();++k)
		{
			CPoint3D lightDir = (Lights[k]->GetPosition() - intersection);
			lightDir.Normalize();
			CRay lightRay(intersection,lightDir);
			CPoint3D lightIntersection;
			CPoint3D lightNormal;
			float lightDist;
			CPrimitive* lightPrimitive = GetClosestShape(lightRay,lightDist,lightIntersection,lightNormal);
			float lightMaxDist = (intersection - Lights[k]->GetPosition()).Length();
			if (!lightPrimitive || lightDist > lightMaxDist)
			{
				// DIFFUSE SHADING
				float dot = lightDir.Dot(normal);
				float dotDiff = dot * primitive->GetMaterial().GetDiffuse();
				if (dotDiff > 0.f)
				{
					color.Red	+= dotDiff * primitive->GetColor().Red	* Lights[k]->GetColor().Red;
					color.Green += dotDiff * primitive->GetColor().Green* Lights[k]->GetColor().Green;
					color.Blue	+= dotDiff * primitive->GetColor().Blue	* Lights[k]->GetColor().Blue;										
				}
				// SPECULAR 
				if (primitive->GetMaterial().GetSpecular() > 0.f)
				{
					CPoint3D reflection = lightDir - normal * 2.0f * dot;
					float dotSpec = reflection.Dot(ray.GetDirection());
					if (dotSpec > 0.f)
					{
						dotSpec = powf(dotSpec,primitive->GetMaterial().GetSpecularExponent()) * primitive->GetMaterial().GetSpecular();
						color.Red	+= dotSpec * primitive->GetColor().Red;
						color.Green += dotSpec * primitive->GetColor().Green;
						color.Blue	+= dotSpec * primitive->GetColor().Blue;
					}

				}			
			}
		}

		// REFLECTION		
		if (primitive->GetMaterial().GetReflection() > 0.f)
		{			
			CPoint3D reflectionDir = ray.GetDirection() - normal * 2 * normal.Dot(ray.GetDirection());
			CRay reflection(intersection, reflectionDir);

			CColor reflectionColor(0.f,0.f,0.f);
			Raytrace(reflection,reflectionColor,depth+1);
			color.Red	+= primitive->GetMaterial().GetReflection() * reflectionColor.Red	* primitive->GetMaterial().GetReflectionColor().Red;
			color.Green += primitive->GetMaterial().GetReflection() * reflectionColor.Green * primitive->GetMaterial().GetReflectionColor().Green;
			color.Blue	+= primitive->GetMaterial().GetReflection() * reflectionColor.Blue	* primitive->GetMaterial().GetReflectionColor().Blue;
		}
		
		// ...
	}
}
CPrimitive* CRaytracer::GetClosestShape(CRay& ray, float& dist, CPoint3D& intersection, CPoint3D& normal)
{
	std::vector<CPrimitive*>& Objects = scene.GetObjects();

	dist = 1e12f;
	CPrimitive* prim=0;
	for (size_t k=0;k<Objects.size();k++)
	{
		CPoint3D tmpIntersection;
		CPoint3D tmpNormal;
		float d = Objects[k]->Intersect(ray,tmpIntersection,tmpNormal);
		if (d > 0.01f && d < dist)
		{
			intersection = tmpIntersection;
			normal = tmpNormal;
			prim = Objects[k];
			dist = d;
		}
	}
	if (dist == 1e12f) dist = -1.f;
	return prim;
}